/**
 * $Id: CompilerElementDoVisitor.java 33 2009-08-15 16:43:34Z vic.fryzel $
 */

package compiler;

import java.util.*;
import parser.*;

/**
 * Represents the visitors for Compiler that visit the nodes in the parse tree.
 * Each method represented here compiles a specific node from the parse tree.
 * 
 * @author Vic Fryzel <vef3240@rit.edu>
 */
public class CompilerElementDoVisitor implements Parser.yyTree.Visitor {

	/**
	 * A stack of maps. Each map is keyed on stack address, and valued on
	 * variable name. This is a stack of maps because we need to put a new map
	 * on the stack for every scope we enter into, and pop it off whenever we
	 * exit a scope. Finally, for the global scope, this is only important for
	 * setting up the final stack to start the program with. Thus, the map at
	 * the bottom of this stack will always be used to generate the stack
	 * member.
	 */
	protected Stack<Map<String, Integer>> stackAddresses;

	/**
	 * The stack used by the generated code.
	 */
	protected Stack<String> stack;

	/**
	 * The current position in the stack. This is incremented every time a push
	 * command is issued. This is used in conjunction with stackAddresses to
	 * allow local vars to be referenced.
	 */
	protected Integer currentStackPos;

	/**
	 * The generated code, in order.
	 */
	protected Stack<String> code;
	
	/**
	 * The temporary code spaces to be used by startTempCode() and 
	 * stopTempCode().
	 */
	protected Stack<Stack<String>> tempCode;

	/**
	 * The constructor for this object. stackAddresses, stack, and code members.
	 */
	public CompilerElementDoVisitor() {
		stackAddresses = new Stack<Map<String, Integer>>();
		stackAddresses.push(new HashMap<String, Integer>());

		tempCode = new Stack<Stack<String>>();

		currentStackPos = 0;
		
		stack = new Stack<String>();
		code = new Stack<String>();
	}

	@Override
	public Object visit(Parser.yyTree.Program node) {
		for (int i = 0; i < node.size(); i++) {
			((Parser.yyTree.Visit) node.get(i)).visit(this);
		}

		/**
		 * The program has been parsed and compiled. Print the compiled stack
		 * and code.
		 */
		String output = getStackString() + "\n" + getCodeString();
		System.out.println(output);

		return node;
	}

	@Override
	public Object visit(Parser.yyTree.Statement node) {
		for (int i = 0; i < node.size(); i++) {
			((Parser.yyTree.Visit) node.get(i)).visit(this);
		}
		return node;
	}

	@Override
	public Object visit(Parser.yyTree.Assign node) {
		Object retval = null;
		if (node.size() == 2) {
			Integer address = stackAddresses.peek().get(
					(String) ((Parser.yyTree.Visit) node.get(0)).visit(this));
			if(address == null) {
				address = currentStackPos;
				this.stackAddresses.peek().put(
						(String) ((Parser.yyTree.Visit) node.get(0)).visit(this),
						address);
				currentStackPos++;
			}
			((Parser.yyTree.Visit) node.get(1)).visit(this);
			code.push("st");
			code.push(address.toString());
			retval = this.stackAddresses.peek().get(node.get(0));
		} else {
			// TODO: Add error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Select node) {
		Object retval = null;
		if (node.size() >= 1) {
			Object conditional = ((Parser.yyTree.Visit) node.get(0))
					.visit(this);
			
			int lastIndex = node.size() - 1;
			boolean foundElse = false;
			
			startTempCode();
			// Start at i = 1 because the i = 0 node was the conditional
			// Stop at -2 because we don't know if last element of node is
			// a SelectElse yet.
			for (int i = 1; i < node.size(); i++) {
				// Not sure why in the interpreter, this is just "SelectElse"
				if(!node.get(i).getClass().getName()
						.equals("Parser$yyTree$SelectElse")) {
					((Parser.yyTree.Visit) node.get(i)).visit(this);
				} else {
					foundElse = true;
				}
			}
			retval = node;
			Stack<String> out = stopTempCode();
			
			code.push("bf");
			// The +1 gets us into the else block or to after the code
			// within this block
			int addr = code.size() + out.size() + 1;
			if(foundElse) {
				// We need to add 2 to this branch if there is an else
				// because the else will start with a branch instruction
				// telling us where to go if the conditional was true
				addr += 2;
			}
			code.push(Integer.toString(addr));
			code.addAll(out);
			
			// If there is a SelectElse, visit it now.
			// The SelectElse will always be the last item in our node's list
			// because of how the grammar is defined.
			// Not sure why in the interpreter, this is just "SelectElse"
			if (node.get(lastIndex).getClass().getName()
					.equals("Parser$yyTree$SelectElse")) {
				((Parser.yyTree.Visit) node.get(node.size() - 1)).visit(this);
			}
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.SelectElse node) {
		startTempCode();
		for (int i = 0; i < node.size(); i++) {
			((Parser.yyTree.Visit) node.get(i)).visit(this);
		}
		Stack<String> out = stopTempCode();	
		// This is for the first block of this select
		// So that if the first block is run, the else block isn't
		code.push("br");
		code.push(Integer.toString(code.size() + out.size() + 1));
		code.addAll(out);
		return node;
	}

	@Override
	public Object visit(Parser.yyTree.Loop node) {
		Object retval = null;
		if (node.size() >= 2) {
			// Store the current pc before we do anything, so we can get back
			// here to do the loop.
			int condAddr = code.size();
			Object conditional = ((Parser.yyTree.Visit) node.get(0))
					.visit(this);
			startTempCode();
			for (int i = 1; i < node.size(); i++) {
				((Parser.yyTree.Visit) node.get(i)).visit(this);
			}
			Stack<String> out = stopTempCode();
			code.push("bf");
			// The +3 gets us past the branch back to the conditional
			// e.g. the actual loop
			int addr = code.size() + out.size() + 3;
			code.push(Integer.toString(addr));
			code.addAll(out);
			code.push("br");
			code.push(Integer.toString(condAddr));
		}
		retval = node;
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Begin node) {
		// Start a new scope with the stackAddresses member
		this.stackAddresses.push(new HashMap<String, Integer>());
		for (int i = 0; i < node.size(); i++) {
			((Parser.yyTree.Visit) node.get(i)).visit(this);
		}
		// Clean up the stack addresses from the child scope
		this.stackAddresses.pop();
		return node;
	}

	@Override
	public Object visit(Parser.yyTree.Print node) {
		for (int i = 0; i < node.size(); i++) {
			((Parser.yyTree.Visit) node.get(i)).visit(this);
			code.push("pr");
		}
		return node;
	}

	@Override
	public Object visit(Parser.yyTree.PrintLn node) {
		for (int i = 0; i < node.size(); i++) {
			((Parser.yyTree.Visit) node.get(i)).visit(this);
			code.push("prln");
		}
		return node;
	}

	@Override
	public Object visit(Parser.yyTree.Expression node) {
		Object retval = null;
		if (node.size() == 1) {
			if (node.get(0).getClass().getName().equals("java.lang.Integer")
					|| node.get(0).getClass().getName().equals(
							"java.lang.String")) {
				retval = node.get(0);
				code.push("push");
				code.push(retval.toString());
			} else {
				retval = ((Parser.yyTree.Visit) node.get(0)).visit(this);
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Add node) {
		Object retval = null;
		if (node.size() >= 2) {
			try {
				retval = (Integer) ((Parser.yyTree.Visit) node.get(0)).visit(this);
			} catch(ClassCastException cce) {
				retval = (String) ((Parser.yyTree.Visit) node.get(0)).visit(this);
			}
			for (int i = 1; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				try {
					retval = (Integer) retval + (Integer) value;
				} catch(ClassCastException cce) {
					retval = (String) retval + (String) value;
				}
				code.push("add");
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Dif node) {
		Integer retval = null;
		if (node.size() >= 2) {
			retval = (Integer) ((Parser.yyTree.Visit) node.get(0)).visit(this);
			for (int i = 1; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				retval -= (Integer) value;
				code.push("sub");
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Mul node) {
		Integer retval = null;
		if (node.size() >= 2) {
			retval = (Integer) ((Parser.yyTree.Visit) node.get(0)).visit(this);
			for (int i = 1; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				retval *= (Integer) value;
				code.push("mul");
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Div node) {
		Integer retval = null;
		if (node.size() >= 2) {
			retval = (Integer) ((Parser.yyTree.Visit) node.get(0)).visit(this);
			for (int i = 1; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				// Disallow divide by zero
				if ((Integer) value != 0) {
					retval /= (Integer) value;
					code.push("div");
				}
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Mod node) {
		Integer retval = null;
		if (node.size() >= 2) {
			retval = (Integer) ((Parser.yyTree.Visit) node.get(0)).visit(this);
			for (int i = 1; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				retval %= (Integer) value;
				code.push("mod");
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Eq node) {
		Object retval = null;
		if (node.size() >= 2) {
			Object prevValue = ((Parser.yyTree.Visit) node.get(0)).visit(this);
			for (int i = 1; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				
				code.push("eq");
				
				if (prevValue == null || !prevValue.equals(value)) {
					retval = Boolean.FALSE;
					break;
				} else {
					retval = Boolean.TRUE;
				}
				prevValue = value;
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Neq node) {
		Object retval = null;
		if (node.size() >= 2) {
			Object prevValue = ((Parser.yyTree.Visit) node.get(0)).visit(this);
			for (int i = 1; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				
				code.push("neq");
				
				if (!prevValue.equals(value)) {
					retval = Boolean.TRUE;
					break;
				} else {
					retval = Boolean.FALSE;
				}
				prevValue = value;
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Gt node) {
		Object retval = null;
		if (node.size() >= 2) {
			Object prevValue = ((Parser.yyTree.Visit) node.get(0)).visit(this);
			for (int i = 1; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				
				code.push("gt");
				
				if ((Integer) prevValue > (Integer) value) {
					retval = Boolean.TRUE;
				} else {
					retval = Boolean.FALSE;
					break;
				}
				prevValue = value;
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Gte node) {
		Object retval = null;
		if (node.size() >= 2) {
			Object prevValue = ((Parser.yyTree.Visit) node.get(0)).visit(this);
			for (int i = 1; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				
				code.push("gte");
				
				if ((Integer) prevValue >= (Integer) value) {
					retval = Boolean.TRUE;
				} else {
					retval = Boolean.FALSE;
					break;
				}
				prevValue = value;
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Lt node) {
		Object retval = null;
		if (node.size() >= 2) {
			Object prevValue = ((Parser.yyTree.Visit) node.get(0)).visit(this);
			for (int i = 1; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				
				code.push("lt");
				
				if ((Integer) prevValue < (Integer) value) {
					retval = Boolean.TRUE;
				} else {
					retval = Boolean.FALSE;
					break;
				}
				prevValue = value;
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Lte node) {
		Object retval = null;
		if (node.size() >= 2) {
			Object prevValue = ((Parser.yyTree.Visit) node.get(0)).visit(this);
			for (int i = 1; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				
				code.push("lte");
				
				if ((Integer) prevValue <= (Integer) value) {
					retval = Boolean.TRUE;
				} else {
					retval = Boolean.FALSE;
					break;
				}
				prevValue = value;
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.And node) {
		Object retval = null;
		if (node.size() >= 2) {
			for (int i = 0; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				if (value == null || value == Boolean.FALSE
						|| value == Integer.valueOf(0)) {
					retval = Boolean.FALSE;
				}
			}
				code.push("and");
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Or node) {
		Object retval = null;
		if (node.size() >= 2) {
			for (int i = 0; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				if (value != null && value != Boolean.FALSE
						&& value != Integer.valueOf(0)) {
					retval = Boolean.TRUE;
				}
			}
			code.push("or");
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.VarName node) {
		Object retval = null;
		if (node.size() == 1) {
			retval = ((String) node.get(0));
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Var node) {
		Object retval = new Integer(0);
		if (node.size() == 1) {
			String name = (String) node.get(0);
			// Iterate up the stackAddresses, find a match if there is one
			Stack<Map<String, Integer>> copy = (Stack<Map<String, Integer>>) this.stackAddresses
					.clone();
			Map<String, Integer> current = copy.pop();
			try {
				while (!current.containsKey(name)) {
					current = copy.pop();
				}
				retval = current.get(name);

				code.push("ld");
				code.push(retval.toString());
			} catch (EmptyStackException ese) {
				// We didn't find a match
				// Do nothing, we already defaulted to 0
			} catch (NullPointerException npe) {
				// Again, no match
				// Already defaulted to 0
			}
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Str node) {
		Object retval = null;
		if (node.size() == 1) {
			retval = ((String) node.get(0));
			code.push("push");
			code.push((String) retval);
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Intg node) {
		Object retval = null;

		if(node.size() == 1) {
			retval = (Integer) node.get(0);
			code.push("push");
			code.push(retval.toString());
		}

		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Bool node) {
		Object retval = null;

		if(node.size() == 1) {
			String val = ((String) node.get(0));
			code.push("push");
			if(val.equalsIgnoreCase("@true")) {
				code.push("true");
				retval = Boolean.TRUE;
			} else if(val.equalsIgnoreCase("@false")) {
				code.push("false");
				retval = Boolean.FALSE;
			}
		}
		
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.AssignArrayEl node) {
		Object retval = new Object();

		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.CreateArray node) {
		Object retval = new Object();

		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.ArrEl node) {
		Object retval = new Object();

		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.ArrayIndex node) {
		Object retval = new Object();

		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Def node) {
		Object retval = new Object();

		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Visit node) {
		return null;
	}

	/**
	 * This starts a temporary code space, so that all visited nodes will 
	 * push their code to this temporary code space.  This allows us to gather
	 * the code for nodes with child blocks, and then use correct addressing.
	 * startTempCode() can be called multiple times in a row, as long as there
	 * is always a matching stopTempCode().  This means that child nodes can 
	 * also call start/stopTempCode() to get their own temporary code spaces,
	 * allowing for nested blocks.
	 *
	 * Usage:
	 * startTempCode();
	 * // visit some nodes
	 * Stack<String> out = stopTempCode();
	 * // out is the code generated by the visited nodes
	 * // this.code does NOT yet contain what's in out
	 * // You must call this.code.addAll(out) to add the code.
	 */
	protected void startTempCode() {
		tempCode.push(code);
		code = new Stack<String>();
	}
	
	/**
	 * Gets the code generated into the temporary code space, and resets
	 * the current code space to be the next temporary code space started, or
	 * the global code space.
	 *
	 * @return The code generated into the temporary code space.
	 */
	protected Stack<String> stopTempCode() {
		Stack<String> retval = code;
		code = tempCode.pop();
		return retval;
	}

	/**
	 * Turns our stack representation into a string that the VM can work with.
	 *
	 * @return A string representation of the stack for the VM to use.
	 */
	protected String getStackString() {
		String retval = "var stack = [\n";
		for (String v : stack) {
			retval += v + ",\n";
		}
		retval += "];";
		return retval;
	}

	/**
	 * Turns our code representation into a string that the VM can run.  This
	 * adds PC values as comments after each line, so that manual evaluation
	 * is a lot easier.
	 *
	 * @return A string representation of the code for the VM to run.
	 */
	protected String getCodeString() {
		String retval = "var code = [\n";
		int i = 0;
		for (String v : code) {
			retval += v + ",\t// " + i + "\n";
			i++;
		}
		retval += "];";
		return retval;
	}
}
